package org.example.jnautil.key;


import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinDef.HMODULE;
import com.sun.jna.platform.win32.WinDef.LRESULT;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.platform.win32.WinUser.HHOOK;
import com.sun.jna.platform.win32.WinUser.KBDLLHOOKSTRUCT;
import com.sun.jna.platform.win32.WinUser.LowLevelKeyboardProc;
import com.sun.jna.platform.win32.WinUser.MSG;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 不支持注册两个以上的组合热键
 */
@Slf4j
public class KeyBoardManager {
    public static KeyBoardManager getInstance() {
        return INSTANCE;
    }

    public static final KeyBoardManager INSTANCE = new KeyBoardManager();
    static final KeyQueueBinder binder = KeyQueueBinder.INSTANCE;
    final HookKeyHandler hookKeyHandler = new HookKeyHandler();

    private KeyBoardManager() {

    }

    /**
     * 用户的使用场景：
     * 你给我一个阻塞队列，如果有这个按键产生了，你就往里面塞消息
     * 1.我想监听Ctrl+C按键，我不关心当前进程如何处理这个按键，而且我的操作不会影响键盘调用
     * 2.我想监听Ctrl+V按键，并且让复制功能不生效
     * 3.我想按下空格+1时，自动填写字符串“斯巴拉西！”，并且不会填写空格和1
     * 组合按键最多支持2个按键组合
     * 组合键的第一个键允许重复，第二个键不允许重复注册，组合按键的第一个键也不允许和其他热键的最后一个键重复
     *
     * @param flag 是否屏蔽原有按键功能
     */
    public synchronized BlockingQueue<String> registerKey(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {
        if (flag) {
            return binder.registerStatefulKey(code1, code2);
        } else {
            return binder.registerStatelessKey(code1, code2);
        }
    }

    public RegisteredKey getRegistered(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {
        for (RegisteredKey r : allListener()) {
            if (Objects.equals(r.firstKey(), code1) &&
                    (code2 == null || Objects.equals(r.secondKey(), code2)) &&
                    Objects.equals(r.isBlocking(), flag)) {
                return r;
            }
        }
        return null;
    }


    public BlockingQueue<String> computeIfAbsent(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {
        RegisteredKey r = getRegistered(code1, code2, flag);
        if (r == null) {
            return registerKey(code1, code2, flag);
        } else {
            return r.getQueue();
        }
    }

    public BlockingQueue<String> computeIfAbsent(RegisteredKey r) {
        return computeIfAbsent(r.firstKey(), r.secondKey(), r.isBlocking());
    }

    public RegisteredKey getRegistered(RegisteredKey r) {
        return getRegistered(r.firstKey(), r.secondKey(), r.isBlocking());
    }


    public synchronized BlockingQueue<String> registerKey(RegisteredKey r) {
        return registerKey(r.firstKey(), r.secondKey(), r.isBlocking());
    }

    public List<RegisteredKey> allListener() {
        return binder.allListener();
    }

    public synchronized void unregisterKey(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {

        try {
            if (flag) {
                binder.unregisterStatefulKey(code1, code2);
            } else {
                binder.unregisterStatelessKey(code1, code2);
            }
        } catch (IllegalArgumentException ex) {

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

    }

    public synchronized void unregisterKey(RegisteredKey r) {
        unregisterKey(r.firstKey(), r.secondKey(), r.isBlocking());
    }

    public synchronized void activeListener(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {
        try {
            if (flag) {
                binder.activeStatefulKey(code1, code2);
            } else {
                binder.activeStatelessKey(code1, code2);
            }
        } catch (IllegalArgumentException ex) {

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public synchronized void activeListener(RegisteredKey r) {
        log.warn("active: {}", r);
        activeListener(r.firstKey(), r.secondKey(), r.isBlocking());

    }

    public synchronized void deactiveListener(KeyCodeEnum code1, KeyCodeEnum code2, boolean flag) {
        try {
            if (flag) {
                binder.deactiveStatefulKey(code1, code2);
            } else {
                binder.deactiveStatelessKey(code1, code2);
            }
        } catch (IllegalArgumentException ex) {

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

    }

    public synchronized void deactiveListener(RegisteredKey r) {
        log.warn("deactive: {}", r);
        deactiveListener(r.firstKey(), r.secondKey(), r.isBlocking());
    }
    // --------------底层api,不受管控-----------------------------------------------

    public boolean addListener2Handler(MyKeyListener l) {
        if (l instanceof StatelessListener) {
            return addStatelessListener((StatelessListener) l);
        } else if (l instanceof StatefulKeyListener) {
            return addStatefulListener((StatefulKeyListener) l);
        } else {
            log.error("未知类型MyKeyListener ：{}", l);
            return false;
        }
    }

    public void delListener2Handler(MyKeyListener l) {
        if (l instanceof StatelessListener) {
            removeStatelessListener((StatelessListener) l);
        } else if (l instanceof StatefulKeyListener) {
            removeStatefulListener((StatefulKeyListener) l);
        } else {
            log.error("未知类型MyKeyListener ：{}", l);
        }
    }

    private synchronized boolean addStatefulListener(StatefulKeyListener listener) {
        if (hookKeyHandler.keyListeners[listener.interestKeyCode] != null) {
            log.error("StatefulListener 一个按键只能添加一个监听器");
            return false;
        }
        hookKeyHandler.keyListeners[listener.interestKeyCode] = listener;
        return true;
    }

    private synchronized void removeStatefulListener(StatefulKeyListener listener) {
        hookKeyHandler.keyListeners[listener.interestKeyCode] = null;
    }

    private synchronized boolean addStatelessListener(StatelessListener listener) {
        StatelessListener[] listeners = hookKeyHandler.statelessListeners;
        for (int i = 0; i < listeners.length; i++) {
            if (listeners[i] == null) {
                listeners[i] = listener;
                return true;
            }
        }
        log.error("StatelessListener 最多添加{}个", listeners.length);
        return false;
    }

    private synchronized void removeStatelessListener(StatelessListener listener) {
        StatelessListener[] listeners = hookKeyHandler.statelessListeners;
        int length = listeners.length;
        for (int i = 0; i < length; i++) {
            if (listeners[i] == listener) {
                listeners[i] = null;
                if (length - 1 - i >= 0) {
                    System.arraycopy(listeners, i + 1, listeners, i, length - 1 - i);
                }
                listeners[length - 1] = null;
                return;
            }
        }
        log.warn("listener： {} 未注册", listener);
    }

    public void keyDown(int keyCode) {
        NativeKeySender.keyDown(keyCode);
    }

    public void keyUp(int keyCode) {
        NativeKeySender.keyUp(keyCode);
    }

    public void pressKey(int keyCode) {
        NativeKeySender.keyDown(keyCode);
        NativeKeySender.keyUp(keyCode);
    }

    public void sendText(String text) {
        NativeKeySender.sendText2(text);
    }

    public void startListenKeyBoard() {
        new Thread(() -> {
            NativeMonitor.start(hookKeyHandler);
        }).start();
    }

    public void stopListenKeyBoard() {
        NativeMonitor.stop();
    }

    static class NativeMonitor {

        static final User32 user32 = User32.INSTANCE;
        static LRESULT not_pass = new LRESULT(1);
        static AtomicBoolean started = new AtomicBoolean();
        static ReentrantLock LOCK = new ReentrantLock();
        private static HHOOK hhk;

        static void start(HookKeyHandler hookKeyHandler) {
            LOCK.lock();
            try {
                if (started.get()) {
                    log.warn("already started");
                    return;
                }
                stop();
                started.set(true);
            } finally {
                LOCK.unlock();
            }
            LowLevelKeyboardProc keyboardHook = new LowLevelKeyboardProc() {

                @Override
                public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) {
                    // 257弹起，256按下
                    boolean keyDown = wParam.intValue() != 257;
                    int vkCode = info.vkCode;
//                    log.debug("native hook 消息，{}, this:{}",vkCode + (keyDown ? "按下" : "弹起"),NativeMonitor.class.hashCode());
//                    System.out.println(vkCode + (keyDown ? "按下" : "弹起"));
                    boolean b = hookKeyHandler.acceptKey(vkCode, keyDown);
                    if (b) {
                        return user32.CallNextHookEx(hhk, nCode, wParam, new WinDef.LPARAM(Pointer.nativeValue(info.getPointer())));
                    } else {
                        return not_pass;
                    }

                }
            };
            HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
            hhk = user32.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);

            // 以下部分是干嘛的？
            int result;
            MSG msg = new MSG();
            while ((result = user32.GetMessage(msg, null, 0, 0)) != 0) {
                if (result == -1) {
                    System.err.println("error in get message");
                    break;
                } else {
                    System.err.println("got message");
                    user32.TranslateMessage(msg);
                    user32.DispatchMessage(msg);
                }
            }
        }

        static void stop() {
            started.set(false);
            if (hhk != null) {
                user32.UnhookWindowsHookEx(hhk);
            }
        }
    }

    static class NativeKeySender {
        public static int STRUCTURE_SIZE;
        static WinUser.INPUT vkInput;
        static WinUser.INPUT[] uniCodeInput;
        static WinUser.INPUT[] unicodeList = (WinUser.INPUT[]) new WinUser.INPUT().toArray(1024 * 2);

        static {
            vkInput = new WinUser.INPUT();
            vkInput.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
            vkInput.input.setType("ki");
            vkInput.input.ki.wScan = new WinDef.WORD(0);
            vkInput.input.ki.time = new WinDef.DWORD(0);
            vkInput.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
            for (int i = 0; i < 1024; i++) {
                WinUser.INPUT input0 = unicodeList[2 * i];
                input0.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
                input0.input.setType("ki");
                input0.input.ki.wVk = new WinDef.WORD(0);
                input0.input.ki.time = new WinDef.DWORD(0);
                input0.input.ki.dwFlags = new WinDef.DWORD(WinUser.KEYBDINPUT.KEYEVENTF_UNICODE);
                input0.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR();

                WinUser.INPUT input1 = unicodeList[2 * i + 1];
                input1.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
                input1.input.setType("ki");
                input1.input.ki.wVk = new WinDef.WORD(0);
                input1.input.ki.time = new WinDef.DWORD(0);
                input1.input.ki.dwFlags = new WinDef.DWORD(WinUser.KEYBDINPUT.KEYEVENTF_UNICODE | WinUser.KEYBDINPUT.KEYEVENTF_KEYUP);
                input1.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR();

            }
            uniCodeInput = (WinUser.INPUT[]) new WinUser.INPUT().toArray(2);
            uniCodeInput[0].type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
            uniCodeInput[0].input.setType("ki");
            uniCodeInput[0].input.ki.wVk = new WinDef.WORD(0);
            uniCodeInput[0].input.ki.time = new WinDef.DWORD(0);
            uniCodeInput[0].input.ki.dwFlags = new WinDef.DWORD(WinUser.KEYBDINPUT.KEYEVENTF_UNICODE);
            uniCodeInput[0].input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR();

            uniCodeInput[1].type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
            uniCodeInput[1].input.setType("ki");
            uniCodeInput[1].input.ki.wVk = new WinDef.WORD(0);
            uniCodeInput[1].input.ki.time = new WinDef.DWORD(0);
            uniCodeInput[1].input.ki.dwFlags = new WinDef.DWORD(WinUser.KEYBDINPUT.KEYEVENTF_UNICODE | WinUser.KEYBDINPUT.KEYEVENTF_KEYUP);
            uniCodeInput[1].input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR();

            STRUCTURE_SIZE = uniCodeInput[0].size();
        }

        static void keyDown(int vkCode) {
            vkInput.input.ki.wVk = new WinDef.WORD(vkCode);
            vkInput.input.ki.dwFlags = new WinDef.DWORD(0);
            User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) vkInput.toArray(1), STRUCTURE_SIZE);
        }

        static void keyUp(int vkCode) {
            vkInput.input.ki.wVk = new WinDef.WORD(vkCode);
            vkInput.input.ki.dwFlags = new WinDef.DWORD(2);

            User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) vkInput.toArray(1), STRUCTURE_SIZE);

        }

        static void sendText(String text) {
            char[] chars = text.toCharArray();
            for (char c : chars) {
                uniCodeInput[0].input.ki.wScan = new WinDef.WORD(c);
                uniCodeInput[1].input.ki.wScan = new WinDef.WORD(c);
                User32.INSTANCE.SendInput(new WinDef.DWORD(2), uniCodeInput, STRUCTURE_SIZE);
            }
        }

        static void sendText2(String text) {
            char[] chars = text.toCharArray();
            int total = chars.length;
            int cur = 0;
            while (cur + 1024 <= total) {
                setChar(chars, cur, cur + 1024);
                cur += 1024;
                WinUser.INPUT[] inputs = unicodeList;

                User32.INSTANCE.SendInput(
                        new WinDef.DWORD(inputs.length), inputs, STRUCTURE_SIZE);
            }
            if (cur < total) {
                setChar(chars, cur, total);
                WinUser.INPUT[] inputs = unicodeList;

                User32.INSTANCE.SendInput(
                        new WinDef.DWORD((total - cur) * 2L), inputs, STRUCTURE_SIZE);
            }
        }

        static void setChar(char[] chars, int start, int end) {
            int len = end - start;
            for (int i = 0; i < len; i++) {
                char c = chars[start + i];
                unicodeList[(2 * i)].input.ki.wScan = new WinDef.WORD(c);
                unicodeList[(2 * i + 1)].input.ki.wScan = new WinDef.WORD(c);
            }
        }
    }
}